package com.crunch.math;

/**
 * Common math operations.
 */
public class MathOps {
	/**
	 * Returns the dot product of {@code a} and {@code b}.
	 */
	public static float dot(Vector2f a, Vector2f b) {
		return a.x()*b.x() + a.y()*b.y();
	}

	/**
	 * Returns the dot product of {@code a} and {@code b}.
	 */
	public static float dot(Vector3f a, Vector3f b) {
		return a.x()*b.x() + a.y()*b.y() + a.z()*b.z();
	}

	/**
	 * Returns the dot product of {@code a} and {@code b}.
	 */
	public static float dot(Vector4f a, Vector4f b) {
		return a.x()*b.x() + a.y()*b.y() + a.z()*b.z() + a.w()*b.w();
	}

	/**
	 * Returns the dot product of {@code a} and {@code b}.
	 */
	public static float dot(Quaternionf a, Quaternionf b) {
		return a.r()*b.r() + a.i()*b.i() + a.j()*b.j() + a.k()*b.k();
	}

	/**
	 * Returns the squared distance between {@code a} and {@code b}.
	 */
	public static float distanceSquared(Vector2f a, Vector2f b) {
		float dx = b.x() - a.x();
		float dy = b.y() - a.y();
		return dx*dx + dy*dy;
	}

	/**
	 * Returns the squared distance between {@code a} and {@code b}.
	 */
	public static float distanceSquared(Vector3f a, Vector3f b) {
		float dx = b.x() - a.x();
		float dy = b.y() - a.y();
		float dz = b.z() - a.z();
		return dx*dx + dy*dy + dz*dz;
	}

	/**
	 * Returns the squared distance between {@code a} and {@code b}.
	 */
	public static float distanceSquared(Vector4f a, Vector4f b) {
		float dx = b.x() - a.x();
		float dy = b.y() - a.y();
		float dz = b.z() - a.z();
		float dw = b.w() - a.w();
		return dx*dx + dy*dy + dz*dz + dw*dw;
	}

	/**
	 * Returns the cross product of {@code a} and {@code b}.
	 */
	public static Vector3f cross(Vector3f a, Vector3f b) {
		return new Vector3f(
				a.y()*b.z() - a.z()*b.y(),
				a.z()*b.x() - a.x()*b.z(),
				a.x()*b.y() - a.y()*b.x());
	}

	/**
	 * Returns a linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static float lerp(float a, float b, float c) {
		return a + (b-a)*c;
	}

	/**
	 * Returns a linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static Vector2f lerp(Vector2f a, Vector2f b, float c) {
		return a.add(b.subtract(a).multiplyAssign(c));
	}

	/**
	 * Returns a linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static Vector3f lerp(Vector3f a, Vector3f b, float c) {
		return a.add(b.subtract(a).multiplyAssign(c));
	}

	/**
	 * Returns a linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static Vector4f lerp(Vector4f a, Vector4f b, float c) {
		return a.add(b.subtract(a).multiplyAssign(c));
	}

	/**
	 * Returns a spherical linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static Quaternionf slerp(Quaternionf a, Quaternionf b, float c) {
		Quaternionf q;
		float dot = dot(a, b);
		if (dot < 0.0f) {
			dot = -dot;
			q = b.negate();
		} else {
			q = b;
		}

		float angle = (float) Math.acos(dot);
		float sinA = (float) Math.sin(angle);
		if (sinA == 0.0f) {
			return new Quaternionf(a);
		} else {
			Quaternionf q1 = a.multiply((float) Math.sin(angle * (1.0f - c)));
			Quaternionf q2 = q.multiply((float) Math.sin(angle * c));
			return q1.add(q2).divideAssign(sinA);
		}
	}

	/**
	 * Returns a normalized linear interpolation of {@code a} and {@code b} by ratio {@code c}.
	 */
	public static Quaternionf nlerp(Quaternionf a, Quaternionf b, float c) {
		float dot = dot(a, b);
		Quaternionf q1 = a.multiply(1.0f - c);
		Quaternionf q2 = b.multiply(c);
		if (dot < 0.0f) {
			q1.subtractAssign(q2);
		} else {
			q1.addAssign(q2);
		}
		q1.normalizeAssign();
		return q1;
	}

	/**
	 * Returns a linear interpolation of the angles {@code a} and {@code b} by ratio {@code c}, taking the shortest
	 * distance. Angles should be in the range [0, 2{@link Math#PI}).
	 */
	public static float lerpAngles(float a, float b, float c) {
		if (a > b) {
			b += 2.0f * (float) Math.PI;
		}
		if (b - a < (float) Math.PI) {
			// interpolate CCW (positive)
			float ret = lerp(a, b, c);
			if (ret >= 2.0f * (float) Math.PI) {
				ret -= 2.0f * (float) Math.PI;
			}
			return ret;
		} else {
			// interpolate CW (negative)
			float ret = lerp(a, b - 2.0f * (float) Math.PI, c);
			if (ret < 0.0f) {
				ret += 2.0f * (float) Math.PI;
			}
			return ret;
		}
	}

	/**
	 * Returns the component-wise min of {@code a} and {@code b}.
	 */
	public static Vector2f min(Vector2f a, Vector2f b) {
		return new Vector2f(Math.min(a.x(), b.x()), Math.min(a.y(), b.y()));
	}

	/**
	 * Returns the component-wise max of {@code a} and {@code b}.
	 */
	public static Vector2f max(Vector2f a, Vector2f b) {
		return new Vector2f(Math.max(a.x(), b.x()), Math.max(a.y(), b.y()));
	}

	/**
	 * Returns the component-wise min of {@code a} and {@code b}.
	 */
	public static Vector3f min(Vector3f a, Vector3f b) {
		return new Vector3f(Math.min(a.x(), b.x()), Math.min(a.y(), b.y()), Math.min(a.z(), b.z()));
	}

	/**
	 * Returns the component-wise max of {@code a} and {@code b}.
	 */
	public static Vector3f max(Vector3f a, Vector3f b) {
		return new Vector3f(Math.max(a.x(), b.x()), Math.max(a.y(), b.y()), Math.max(a.z(), b.z()));
	}

	/**
	 * Returns the component-wise min of {@code a} and {@code b}.
	 */
	public static Vector4f min(Vector4f a, Vector4f b) {
		return new Vector4f(Math.min(a.x(), b.x()), Math.min(a.y(), b.y()), Math.min(a.z(), b.z()), Math.min(a.w(), b.w()));
	}

	/**
	 * Returns the component-wise max of {@code a} and {@code b}.
	 */
	public static Vector4f max(Vector4f a, Vector4f b) {
		return new Vector4f(Math.max(a.x(), b.x()), Math.max(a.y(), b.y()), Math.max(a.z(), b.z()), Math.max(a.w(), b.w()));
	}

	/**
	 * Returns the value {@code v} clamped to the range {@code [a,b]}. The result is undefined if {@code a > b}.
	 */
	public static float clamp(float v, float a, float b) {
		return Math.max(a, Math.min(b, v));
	}

	/**
	 * Returns the value {@code v} clamped component-wise to the range {@code [a,b]}. The result is undefined if {@code a[i] > b[i]}.
	 */
	public static Vector2f clamp(Vector2f v, Vector2f a, Vector2f b) {
		return new Vector2f(
				clamp(v.x(), a.x(), b.x()),
				clamp(v.y(), a.y(), b.y()));
	}

	/**
	 * Returns the value {@code v} clamped component-wise to the range {@code [a,b]}. The result is undefined if {@code a[i] > b[i]}.
	 */
	public static Vector3f clamp(Vector3f v, Vector3f a, Vector3f b) {
		return new Vector3f(
				clamp(v.x(), a.x(), b.x()),
				clamp(v.y(), a.y(), b.y()),
				clamp(v.z(), a.z(), b.z()));
	}

	/**
	 * Returns the value {@code v} clamped component-wise to the range {@code [a,b]}. The result is undefined if {@code a[i] > b[i]}.
	 */
	public static Vector4f clamp(Vector4f v, Vector4f a, Vector4f b) {
		return new Vector4f(
				clamp(v.x(), a.x(), b.x()),
				clamp(v.y(), a.y(), b.y()),
				clamp(v.z(), a.z(), b.z()),
				clamp(v.w(), a.w(), b.w()));
	}
}
